var Stage = function() {
    this.stage = this;

    Stage.parent.call(this);

    this.children = [];
    this.defaultWatcherX = 10;
    this.defaultWatcherY = 10;

    this.info = {};
    this.answer = '';
    this.promptId = 0;
    this.nextPromptId = 0;
    this.tempoBPM = 60;
    this.videoAlpha = 1;
    this.zoomX = 1;
	  this.zoomY = 1;
    this.maxZoomX = SCALE;
	  this.maxZoomY = SCALE;
    this.baseNow = 0;
    this.baseTime = 0;
    this.timerStart = 0;

    this.keys = []
    this.keys[128] = 0;
    this.rawMouseX = 0;
    this.rawMouseY = 0;
    this.mouseX = 0;
    this.mouseY = 0;
    this.mousePressed = false;
			this.alpha = 0;
			this.beta = 1;
			this.gamma = 2;

    this.root = document.createElement('div');
    this.root.style.position = 'absolute';
    this.root.style.overflow = 'hidden';
    this.root.style.width = '480px';
    this.root.style.height = '360px';
    this.root.style.fontSize = '10px';
    this.root.style.background = '#fff';
    this.root.style.WebkitUserSelect =
    this.root.style.MozUserSelect =
    this.root.style.MSUserSelect =
    this.root.style.WebkitUserSelect = 'none';

    /********************   BACKDROP Canvas   ********************/
    
    this.backdropCanvas = document.createElement('canvas');
    this.root.appendChild(this.backdropCanvas);
    this.backdropCanvas.width = SCALE * 480;
    this.backdropCanvas.height = SCALE * 360;
    this.backdropCanvas.setAttribute('id', 'backdropCanvas');
    //this.backdropContext = this.backdropCanvas.getContext('2d');
    this.backdropContext = this.backdropCanvas.getContext('webgl') ||
                           this.backdropCanvas.getContext('experimental-webgl');
    
    if(!this.backdropContext) P.showWebGLError('backdropContext could not be initialized.');
    
    this.backdropContext.imgShader = initShaderProgram(this.backdropContext, Shader.imgVert, Shader.imgFrag);
    
    this.backdropContext.imgShaderInfo = {
      program: this.backdropContext.imgShader,
      attribLocations: {
        position: this.backdropContext.getAttribLocation(this.backdropContext.imgShader, 'position'),
        texcoord: this.backdropContext.getAttribLocation(this.backdropContext.imgShader, 'texcoord'),
      },
      uniformLocations: {
        matrix:      this.backdropContext.getUniformLocation(this.backdropContext.imgShader, 'u_matrix'),
        texture:     this.backdropContext.getUniformLocation(this.backdropContext.imgShader, 'u_texture'),
        texSize:     this.backdropContext.getUniformLocation(this.backdropContext.imgShader, 'texSize'),
        colorEffect: this.backdropContext.getUniformLocation(this.backdropContext.imgShader, 'colorEffect'),
        colorMatrix: this.backdropContext.getUniformLocation(this.backdropContext.imgShader, 'colorMatrix'),
        texEffect:   this.backdropContext.getUniformLocation(this.backdropContext.imgShader, 'texEffect'),        
      },
      blendSource: this.backdropContext.SRC_ALPHA,
      blendDest: this.backdropContext.ONE_MINUS_SRC_ALPHA,      
    }
    this.backdropContext.imgBuffers = initImgBuffers(this.backdropContext);
    
    /********************   PEN Canvas   ********************/
    
    this.penCanvas = document.createElement('canvas');
    this.root.appendChild(this.penCanvas);
    this.penCanvas.width = SCALE * 480;
    this.penCanvas.height = SCALE * 360;
    this.penCanvas.setAttribute('id', 'penCanvas');
    //this.penCanvas.setAttribute('style', 'display: none');
    //this.penContext = this.penCanvas.getContext('2d');
    //this.penContext.lineCap = 'butt';
    //this.penContext.scale(SCALE, SCALE);
    this.penContext = this.penCanvas.getContext('webgl', {preserveDrawingBuffer: true}) ||
                      this.penCanvas.getContext('experimental-webgl', {preserveDrawingBuffer: true});
    
    if(!this.penContext) P.showWebGLError('penContext could not be initialized.');
    
		// Scene is automatically redrawn when arrays are full.
		// Thus, we don't need to call new float32Array() when creating the VBOs,
		// which would cause stutter due to garbage collection.
		//
		// Possibly tweak values for optimal performance.
    this.penCoords = new Float32Array(65536);
    this.penLines  = new Float32Array(32768);
    this.penColors = new Float32Array(65536);
    this.penCoordIndex = 0;
    this.penLineIndex  = 0;
    this.penColorIndex = 0;
    
    // Load and compile shaders
    this.penContext.penShader = initShaderProgram(this.penContext, Shader.penVert, Shader.penFrag);
    
    this.penContext.penShaderInfo = {
      program: this.penContext.penShader,
      attribLocations: {
        vertexData:       this.penContext.getAttribLocation(this.penContext.penShader, 'vertexData'),
        lineData:         this.penContext.getAttribLocation(this.penContext.penShader, 'lineData'),
        colorData:        this.penContext.getAttribLocation(this.penContext.penShader, 'colorData'),
      },
      uniformLocations: {
        projectionMatrix: this.penContext.getUniformLocation(this.penContext.penShader, 'uProjectionMatrix'),
        modelViewMatrix:  this.penContext.getUniformLocation(this.penContext.penShader, 'uModelViewMatrix'),
      },     
    };  
    this.penContext.penBuffers = {
      position: this.penContext.createBuffer(),
      line: this.penContext.createBuffer(),
      color: this.penContext.createBuffer(), 
    };
    
    
    this.penContext.imgShader = initShaderProgram(this.penContext, Shader.imgVert, Shader.imgFrag);
    
    this.penContext.imgShaderInfo = {
      program: this.penContext.imgShader,
      attribLocations: {
        position: this.penContext.getAttribLocation(this.penContext.imgShader, 'position'),
        texcoord: this.penContext.getAttribLocation(this.penContext.imgShader, 'texcoord'),
      },
      uniformLocations: {
        matrix:      this.penContext.getUniformLocation(this.penContext.imgShader, 'u_matrix'),
        texture:     this.penContext.getUniformLocation(this.penContext.imgShader, 'u_texture'),
        texSize:     this.penContext.getUniformLocation(this.penContext.imgShader, 'texSize'),
        colorEffect: this.penContext.getUniformLocation(this.penContext.imgShader, 'colorEffect'),
        colorMatrix: this.penContext.getUniformLocation(this.penContext.imgShader, 'colorMatrix'),
        texEffect:   this.penContext.getUniformLocation(this.penContext.imgShader, 'texEffect'),             
      },
      blendSource: this.penContext.SRC_ALPHA,
      blendDest: this.penContext.ONE_MINUS_SRC_ALPHA,      
    }
    this.penContext.imgBuffers = initImgBuffers(this.penContext);
    
    /********************   COSTUME Canvas   ********************/
    
    this.canvas = document.createElement('canvas');
    this.root.appendChild(this.canvas);
    this.canvas.width = SCALE * 480;
    this.canvas.height = SCALE * 360;
    this.canvas.setAttribute('id', 'canvas');
    //this.context = this.canvas.getContext('2d');
    this.context = this.canvas.getContext('webgl') ||
                   this.canvas.getContext('experimental-webgl');
    
    if(!this.context) P.showWebGLError('context could not be initialized.');
    
    this.context.imgShader = initShaderProgram(this.context, Shader.imgVert, Shader.imgFrag);
    
    this.context.imgShaderInfo = {
      program: this.context.imgShader,
      attribLocations: {
        position:    this.context.getAttribLocation(this.context.imgShader, 'position'),
        texcoord:    this.context.getAttribLocation(this.context.imgShader, 'texcoord'),
      },
      uniformLocations: {
        matrix:      this.context.getUniformLocation(this.context.imgShader, 'u_matrix'),
        texture:     this.context.getUniformLocation(this.context.imgShader, 'u_texture'),
        texSize:     this.context.getUniformLocation(this.context.imgShader, 'texSize'),
        colorEffect: this.context.getUniformLocation(this.context.imgShader, 'colorEffect'),
        colorMatrix: this.context.getUniformLocation(this.context.imgShader, 'colorMatrix'),
        texEffect:   this.context.getUniformLocation(this.context.imgShader, 'texEffect'),
      },
      blendSource: this.context.SRC_ALPHA,
      blendDest: this.context.ONE_MINUS_SRC_ALPHA,      
    }
    this.context.imgBuffers = initImgBuffers(this.context);
    
    /********************   COLLISION Canvas   ********************/
    
    this.glCollisionCanvas = document.createElement('canvas');
    this.root.appendChild(this.glCollisionCanvas);
    this.glCollisionCanvas.width = 480;
    this.glCollisionCanvas.height = 360;
    this.glCollisionCanvas.setAttribute('id', 'glCollisionCanvas');
    this.glCollisionCanvas.setAttribute('style', 'display: none;');
    this.glCollisionContext = this.glCollisionCanvas.getContext('webgl', {preserveDrawingBuffer: true}) ||
                              this.glCollisionCanvas.getContext('experimental-webgl', {preserveDrawingBuffer: true});
    
    if(!this.glCollisionContext) P.showWebGLError('glCollisionContext could not be initialized.');

    //Scissor test for faster collision detection.
    this.stage.glCollisionContext.enable(this.stage.glCollisionContext.SCISSOR_TEST);    
    this.stage.glCollisionContext.scissor(0, 0, 480, 360);
 
    this.glCollisionContext.imgShader = initShaderProgram(this.glCollisionContext, Shader.imgVert, Shader.imgFrag);
    
    this.glCollisionContext.imgShaderInfo = {
      program: this.glCollisionContext.imgShader,
      attribLocations: {
        position:    this.glCollisionContext.getAttribLocation(this.glCollisionContext.imgShader, 'position'),
        texcoord:    this.glCollisionContext.getAttribLocation(this.glCollisionContext.imgShader, 'texcoord'),
      },
      uniformLocations: {
        matrix:      this.glCollisionContext.getUniformLocation(this.glCollisionContext.imgShader, 'u_matrix'),
        texture:     this.glCollisionContext.getUniformLocation(this.glCollisionContext.imgShader, 'u_texture'),
        texSize:     this.glCollisionContext.getUniformLocation(this.glCollisionContext.imgShader, 'texSize'),
        colorEffect: this.glCollisionContext.getUniformLocation(this.glCollisionContext.imgShader, 'colorEffect'),
        colorMatrix: this.glCollisionContext.getUniformLocation(this.glCollisionContext.imgShader, 'colorMatrix'),
        texEffect:   this.glCollisionContext.getUniformLocation(this.glCollisionContext.imgShader, 'texEffect'),
      },  
      blendSource: this.glCollisionContext.SRC_ALPHA,
      blendDest: this.glCollisionContext.ONE_MINUS_SRC_ALPHA,
    }
    this.glCollisionContext.imgBuffers = initImgBuffers(this.glCollisionContext);    
    
    
    
    this.glCollisionContext.touchingShader = initShaderProgram(this.glCollisionContext, Shader.touchingVert, Shader.touchingFrag);
    
    this.glCollisionContext.touchingShaderInfo = {
      program: this.glCollisionContext.touchingShader,
      attribLocations: {
        position:    this.glCollisionContext.getAttribLocation(this.glCollisionContext.touchingShader, 'position'),
        texcoord:    this.glCollisionContext.getAttribLocation(this.glCollisionContext.touchingShader, 'texcoord'),        
      },
      uniformLocations: {
        matrix:      this.glCollisionContext.getUniformLocation(this.glCollisionContext.touchingShader, 'u_matrix'),
        texture:     this.glCollisionContext.getUniformLocation(this.glCollisionContext.touchingShader, 'u_texture'),
        tColor:      this.glCollisionContext.getUniformLocation(this.glCollisionContext.touchingShader, 'tColor'),
        texSize:     this.glCollisionContext.getUniformLocation(this.glCollisionContext.touchingShader, 'texSize'),
        colorEffect: this.glCollisionContext.getUniformLocation(this.glCollisionContext.touchingShader, 'colorEffect'),
        colorMatrix: this.glCollisionContext.getUniformLocation(this.glCollisionContext.touchingShader, 'colorMatrix'),
        texEffect:   this.glCollisionContext.getUniformLocation(this.glCollisionContext.touchingShader, 'texEffect'),   
      },
      blendSource: this.glCollisionContext.DST_ALPHA,
      blendDest: this.glCollisionContext.ZERO,      
    }
    
    
    this.canvas.tabIndex = 0;
    this.canvas.style.outline = 'none';
    this.backdropCanvas.style.position =
    this.penCanvas.style.position =
    this.canvas.style.position =
    this.glCollisionCanvas.style.position = 'absolute';
    this.backdropCanvas.style.width =
    this.penCanvas.style.width =
    this.canvas.style.width =
    this.glCollisionCanvas.style.width = '480px';
    this.backdropCanvas.style.height =
    this.penCanvas.style.height =
    this.canvas.style.height =
    this.glCollisionCanvas.style.height = '360px';
    
    //this.glCollisionCanvas.style.width = '240px';
    //this.glCollisionCanvas.style.height = '180px';
    
    this.backdropContext.clearColor(0.0, 0.0, 0.0, 0.0);
    this.penContext.clearColor(0.0, 0.0, 0.0, 0.0);
    this.context.clearColor(0.0, 0.0, 0.0, 0.0);
    this.glCollisionContext.clearColor(0.0, 0.0, 0.0, 0.0);

    // hardware acceleration
    this.root.style.WebkitTransform = 'translateZ(0)';

    // added old way here and split...
	  
     this.root.addEventListener('keypress', function(e) { // pf shift symbols helper.
       if (ASCII) {
	 
           if (e.altKey || e.metaKey || e.keyCode === 27) { // tjvr
             //return; // PF allow e.ctrlKey || allow e.shiftkey
           }
           var key = e.keyCode;

           //console.log(this.keys[key]); // debug only
           if (e.target === this.canvas && !this.keys[key]) {

	     ShiftKey = false;
	     if (key > 64 && key < 91) {
	       ShiftKey = true;	 
	     }		   
		   
	     this.keys[key] = true; // mandatory for symbols
	     self.key = key; // resets symbol keys
	     e.stopPropagation();
             e.preventDefault();
             //this.trigger('whenKeyPressed', key); // *
           }
	 
       } else {
	     // TODO: as before (not needed)      
       }	       
    }.bind(this));

    this.root.addEventListener('keydown', function(e) { // pf inc. arrow keys and shift key mapper
      if (ASCII) {

          if (e.altKey || e.metaKey || e.keyCode === 27) { // tjvr
            return; // PF allow e.ctrlKey || 
          }
          var key = e.keyCode;
	        //console.log(key); // debug only
          e.stopPropagation();
          if (e.target === this.canvas && !this.keys[key] && "16.17.37.38.39.40".match(key.toString())) { // 
	    if (key == 16) key = 128; // (Shift key hack) was 0
	    //if (key == 17) key = 0;  
	    if (key == 37) key = 28;
	    if (key == 39) key = 29;
	    if (key == 38) key = 30;
	    if (key == 40) key = 31;
	    this.keys[key] = true; // pf done in keypress?
	    self.key = key;
            e.preventDefault();
	    if (ShiftKey) {
	      //console.log("Shift Pressed\n"); // debug only
              this.trigger('whenKeyPressed', 128);
	      //this.trigger('whenKeyPressed', key);
	    } else {
	      this.trigger('whenKeyPressed', key);	    
	    }
          }
	
      } else {
        // TODO: as before    
        if (e.altKey || e.metaKey || e.keyCode === 27) { // tjvr
          return; // PF allow e.ctrlKey || 
        }
        //console.log(e.keyCode)+"\n";
        this.keys[e.keyCode] = true;
        e.stopPropagation();
        if (e.target === this.canvas) {
          e.preventDefault();
          this.trigger('whenKeyPressed', e.keyCode);
        }	       
      }	       
    }.bind(this));	  
	  
    this.root.addEventListener('keyup', function(e) {
      if (ASCII) {
    
          var key = e.keyCode;
	  if (key == 16) key = 128; 
          //console.log(key); // db2
          this.keys[key] = false;
          if (key > 64 && key < 91) this.keys[key+32] = false; // was +32
          this.keys[self.key] = false;
          if (ShiftKey) {
	    //this.keys[128] = false;
	  } else {
	    //console.log (self.key + " :: " + key); // debug only
	  }
          e.stopPropagation();
          if (e.target === this.canvas) {
            e.preventDefault();
          }

      } else {
	// TODO: as before   
        this.keys[e.keyCode] = false;
        e.stopPropagation();
        if (e.target === this.canvas) {
          e.preventDefault();
        }	       
      }
    }.bind(this));
	
	//Changed this to include both event listeners, otherwise Hybrid laptops may not work. Possibly add extra option for hybrids instead.
    //if (hasTouchEvents) {

      document.addEventListener('touchstart', function(e) {
        this.mousePressed = true;
        for (var i = 0; i < e.changedTouches.length; i++) {
          this.updateMouse(e.changedTouches[i]);
          if (e.target === this.canvas) {
            this.clickMouse();
          }
        }
        if (e.target === this.canvas) e.preventDefault();
      }.bind(this));

      document.addEventListener('touchmove', function(e) {
        this.updateMouse(e.changedTouches[0]);
      }.bind(this));

      document.addEventListener('touchend', function(e) {
        this.releaseMouse();
      }.bind(this));
	  
	  // Eventlistener for starting audio on mobile.
	  document.addEventListener('touchend', function(e) {
		if(!audioContext.mInit){
		  audioContext.mInit = true;
		  var osc = audioContext.createOscillator();
		  osc.frequency.value = 0;
		  osc.connect(audioContext.destination);
		  osc.start(0);
		  osc.stop(0);
		}
	  }.bind(this));
	  
	  
	  //if(hasTouchEvents){

    //} else {

      document.addEventListener('mousedown', function(e) {
        this.updateMouse(e);
        this.mousePressed = true;

        if (e.target === this.canvas) {
          this.clickMouse();
          e.preventDefault();
          this.canvas.focus();
        }
      }.bind(this));

      document.addEventListener('mousemove', function(e) {
        this.updateMouse(e);
      }.bind(this));

      document.addEventListener('mouseup', function(e) {
        this.updateMouse(e);
        this.releaseMouse();
      }.bind(this));
    //}

	
	
    this.prompter = document.createElement('div');
    this.root.appendChild(this.prompter);
    this.prompter.style.zIndex = '1';
    this.prompter.style.pointerEvents = 'auto';
    this.prompter.style.position = 'absolute';
    this.prompter.style.left =
    this.prompter.style.right = '1.4em';
    this.prompter.style.bottom = '.6em';
    this.prompter.style.padding = '.5em 3.0em .5em .5em';
    this.prompter.style.border = '.3em solid rgb(46, 174, 223)';
    this.prompter.style.borderRadius = '.8em';
    this.prompter.style.background = '#fff';
    this.prompter.style.display = 'none';

    this.promptTitle = document.createElement('div');
    this.prompter.appendChild(this.promptTitle);
    this.promptTitle.textContent = '';
    this.promptTitle.style.cursor = 'default';
    this.promptTitle.style.font = 'bold 1.3em sans-serif';
    this.promptTitle.style.margin = '0 '+(-25/13)+'em '+(5/13)+'em 0';
    this.promptTitle.style.whiteSpace = 'pre';
    this.promptTitle.style.overflow = 'hidden';
    this.promptTitle.style.textOverflow = 'ellipsis';

    this.prompt = document.createElement('input');
    this.prompter.appendChild(this.prompt);
    this.prompt.style.border = '0';
    this.prompt.style.background = '#eee';
    this.prompt.style.MozBoxSizing =
    this.prompt.style.boxSizing = 'border-box';
    this.prompt.style.font = '1.3em sans-serif';
    this.prompt.style.padding = '0 '+(3/13)+'em';
    this.prompt.style.outline = '0';
    this.prompt.style.margin = '0';
    this.prompt.style.width = '100%';
    this.prompt.style.height = ''+(20/13)+'em';
    this.prompt.style.display = 'block';
    this.prompt.style.WebkitBorderRadius =
    this.prompt.style.borderRadius = '0';
    this.prompt.style.WebkitBoxShadow =
    this.prompt.style.boxShadow = 'inset '+(1/13)+'em '+(1/13)+'em '+(2/13)+'em rgba(0, 0, 0, .2), inset '+(-1/13)+'em '+(-1/13)+'em '+(1/13)+'em rgba(255, 255, 255, .2)';
    this.prompt.style.WebkitAppearance = 'none';

    this.promptButton = document.createElement('div');
    this.prompter.appendChild(this.promptButton);
    this.promptButton.style.width = '2.2em';
    this.promptButton.style.height = '2.2em';
    this.promptButton.style.position = 'absolute';
    this.promptButton.style.right = '.4em';
    this.promptButton.style.bottom = '.4em';
    this.promptButton.style.background = 'url(/img/icons.svg) -16.5em -3.7em';
    this.promptButton.style.backgroundSize = '32.0em 9.6em';

    this.prompt.addEventListener('keydown', function(e) {
      if (e.keyCode === 13) {
        this.submitPrompt();
      }
    }.bind(this));

    this.promptButton.addEventListener(hasTouchEvents ? 'touchstart' : 'mousedown', this.submitPrompt.bind(this));

    this.initRuntime();
  };
  inherits(Stage, Base);

  Stage.prototype.isStage = true;

  
  Stage.prototype.initLists = function () {
    var show = false; // init show / hide of all stage and childrens lists
    var name = false;
    var o_list = this.lists;
    var o_listInfo = this.listsInfo; // may need to loop this?
  
    if (o_list && o_listInfo) {
      for (var key in o_listInfo) {
        var obj = o_listInfo[key];
	for (var prop in obj) {
	 // skip loop if the property is from prototype //console.log(prop + " = " + obj[prop]);
	  if (!obj.hasOwnProperty(prop)) {
	    continue;
	  }
	  if (obj[prop].toString() == "t") {
	    console.log("List: " + key + " = true");
	    
		this.showList(key);
	    break;
	  }
	}
      }	     
    }
  	  
    var oc_list;
    var oc_listInfo;
    // loop around children
    for (var oc = 0; oc < this.children.length; oc++) {
      oc_listInfo = this.children[oc].listsInfo;
      if (oc_listInfo) {	     
	for (var key in o_listInfo) {
	  var obj = oc_listInfo[key];
	  for (var prop in obj) {
          // skip loop if the property is from prototype //console.log(prop + " = " + obj[prop]);
	    if (!obj.hasOwnProperty(prop)) {
	      continue;
	    }
	    if (obj[prop].toString() == "t") {
	      console.log("List: " + key + " = true");
	      this.showList(key);
	      break;
	    }
	  }
	}
      }
    }
  };
  
  Stage.prototype.updateList = function (name) {
    // this function is a potential performance killer, so only invoke if 'name' list is showing...
    if (document.getElementById(name)) {
      var show = false; // init show / hide of all stage and childrens lists
      //var name = false;
      var o_list = this.lists;
      var o_listInfo = this.listsInfo; // may need to loop this?
  
      if (o_list && o_listInfo) {
        for (var key in o_listInfo) {
          var obj = o_listInfo[key];
	  for (var prop in obj) {
	   // skip loop if the property is from prototype //console.log(prop + " = " + obj[prop]);
	    if (!obj.hasOwnProperty(prop)) {
	      continue;
	    }
	    if (obj[prop].toString() == "t") {
	      console.log("List: " + key + " = true");
	      if (key == name) {
	        this.showList(key);
	        break;	      
              }
            }
          }
        }	     
      }
    }
  };

  // pf new way - works with scaling via em's (was px)
  Stage.prototype.showList = function(name) {
    console.log("Show List:" + name + " isTurbo:" + this.isTurbo); // if turbo mode then only draw list every 4 ticks?

    var o_div_test = document.getElementById(name);
    if (this.isTurbo && o_div_test && (Date.now()%1024) <= 1000) return; //console.log("### RENDER ###");

    if (o_div_test) {
      console.log("List already rendered. DOM");
      this.stage.root.removeChild(o_div_test);
    }
	
    var o_list = (this.lists[name]) ? this.lists[name] : this.lists[name];
    var o_listInfo = (this.listsInfo[name]) ? this.listsInfo[name] : this.listsInfo[name];	  
    if (o_list && o_listInfo) {
     /* for (var ol = 0; ol < o_list.length; ol++) {
       * console.log((ol+1) + " : " + o_list[ol]+"\n");
      }*/
      console.log(o_listInfo+"\n");
	  
    } else {
      for (var oc = 0; oc < this.children.length; oc++) {
        if (this.children[oc].lists && this.children[oc].lists[name]) { // pf ###
          o_list = this.children[oc].lists[name];
	  o_listInfo = this.children[oc].listsInfo[name];
          break;
	}
      }
      if (o_list) {
        for (var ol = 0; ol < o_list.length; ol++) {
          console.log((ol+1) + " :: " + o_list[ol]+"\n");
        }
	console.log(o_listInfo+"\n");
      } 
    }
	  
    if (o_list && o_listInfo) {
	// display list using divs. Thanks to Dogtopius for the CSS colours!
	var info = o_listInfo.split(",");    
	var show = !!(o_listInfo.match("true"));
	var divContainer = document.createElement('div');
	var overflow = (2 + parseInt(info[0], 10) + parseInt(info[2], 10)) - 480; // border + left + width needs to be - 480px
	divContainer.id = name;
	divContainer.style.border = "solid #949191 2px"; // 
	divContainer.style.margin = "5px";
	divContainer.style.padding = "0";
	divContainer.style.borderRadius = "7px";
        divContainer.style.backgroundColor = "#c1c4c7";
	divContainer.style.position = 'absolute';
	divContainer.style.overflow = 'hidden';
	divContainer.style.left = (info[0] - 7) + 'px'; // border + margin
	divContainer.style.top = (info[1] - 7) + 'px'; // border + margin 
	if (overflow > 0) { // disable?
		divContainer.style.width = (info[2] - overflow) + 'px'; // if left + width > 480 then adjust to be < 480	    
			
	
	} else {
		divContainer.style.width = info[2] + 'px';
	}
	if (o_list.length) divContainer.style.height = info[3] + 'px';
	divContainer.innerHTML = "<div style='margin: 2px'><span style='font-size: 12px; text-align: center; font-weight: bold;'><center>" + name + "</center></span></div>";
	    
	var divHolder = this.stage.root.appendChild(divContainer); // or this.stage.canvas.parentNode;
	var divInner = document.createElement('div');
	divInner.style.position = 'relative';
	divInner.style.overflow = 'auto';
	divInner.style.height = '86%'; // as before (magic number!)

	var divItem;
	var replaced;
	
	for (var i = 0; i < o_list.length; i++) { // test
	  divItem = document.createElement('div');
	  divItem.style.backgroundColor = "#c1c4c7";
	  try {replaced = o_list[i].replace(/'/g, "&#39;");} catch(e) {replaced = o_list[i];} // pf fix replace
	  //if (typeof o_list[i] == "undefined") {replaced = o_list[i];} else {replaced = o_list[i].replace(/'/g, "&#39;");} // pf fix replace !worky
	  divItem.innerHTML = "<input readonly value=' " + (i + 1) + "' style='color: #000; border: 0; background-color: #c1c4c7; width: 10%; font-size: 11px; margin: 1px'/> <input readonly value='" + replaced + "' style='font-size: 12px; background-color: #cc5b22; color: white; width: 75%; height: 10px; border: 1px solid #fff; border-radius: 3px; padding: 3px; margin: 0px;' />"; // TODO: rid 75% width and calc instead!
	  divInner.appendChild(divItem);	
	}
	    
	var divItem2 = document.createElement('div');
	//divItem2.style.position = 'relative';
	if (o_list.length) {
	  if ( o_list.length > (parseInt(info[3],10) / 22) ) { // magic number! 'calc text px size as ~ number of elements'    
		console.log("Long List!"); 
		divInner.style.height = (parseInt(info[3],10) - 40) + "px"; // magic number! 'px gap to remove to stop clash'
	  }
	  divItem2.innerHTML = "<div style='font-size: 11px; text-align: center; bottom: 2px; position: absolute; width: 100%;'>" +  "length: " + o_list.length + "</div>";
	} else {
	  var hem = info[3] > 270 ? 93 : 89; // help! (more magic tomfoolery)
	  var pem = ( (info[3] / 100) * hem ) + 0; // qtest 
	  console.log("HEIGHT=" + info[3] + " pem=" + pem);
	  if (parseInt(info[1], 10) + parseInt(info[3], 10) < 360) { // !offscreen
	    divItem = document.createElement('div');
	    divItem.style.height = pem + 'px';
	    divItem.innerHTML = "<div style='padding-top: " + (pem / 2.4) + "px'><div style='font-size: 11px; text-align: center;'>(empty)</div></div>"; // 
	    divInner.appendChild(divItem);
	    divItem2.innerHTML = "<div style='font-size: 11px; text-align: center; bottom: 2px; position: absolute; width: 100%;'>length: 0</div>";
	  } else {
	    // old way...	
	    divItem2.innerHTML = "<div style='font-size: 11px; text-align: center;'><br><br>(empty)</div><div style='font-size: 11px; text-align: center; padding-bottom: 0.1px'><br><br>length: 0</div>"; 
	  }
	}
	divHolder.appendChild(divInner);
        divHolder.appendChild(divItem2);
    }
	if (this.saying) this.updateBubble();	  
  };
	
  Stage.prototype.hideList = function(name) {
     console.log("Hide List:" + name);
     var o_div = document.getElementById(name);
     if (o_div) this.stage.root.removeChild(o_div);
  };	
	
  
  
  
  
  Stage.prototype.fromJSON = function(data) {
    Stage.parent.prototype.fromJSON.call(this, data);

    data.children.forEach(function(d) {
      if (d.listName) return;
      this.children.push(new (d.cmd ? Watcher : Sprite)(this).fromJSON(d));
    }, this);

    this.children.forEach(function(child) {
      if (child.resolve) child.resolve();
    }, this);

    P.compile(this);
	
    return this;
  };

  Stage.prototype.focus = function() {
    if (this.promptId < this.nextPromptId) {
      this.prompt.focus();
    } else {
      this.canvas.focus();
    }
  };

  Stage.prototype.updateMouse = function(e) {
    var bb = this.canvas.getBoundingClientRect();
	 var z = Math.max(this.zoomX, this.zoomY);
    var x = (e.clientX - bb.left) / z - 240;
    var y = 180 - (e.clientY - bb.top) / z;
    this.rawMouseX = x;
    this.rawMouseY = y;
    if (x < -240) x = -240;
    if (x > 240) x = 240;
    if (y < -180) y = -180;
    if (y > 180) y = 180;
    this.mouseX = x;
    this.mouseY = y;
  };

  Stage.prototype.updateOrientation = function(data) {
			self.stage.alpha = data.do.alpha;
			self.stage.beta = data.do.beta;
			self.stage.gamma = data.do.gamma;
	}
  
  
  Stage.prototype.updateBackdrop = function() {
    this.backdropCanvas.width = this.zoomX * SCALE * 480;
    this.backdropCanvas.height = this.zoomY * SCALE * 360;
    var costume = this.costumes[this.currentCostumeIndex];
    
    /*
    this.backdropContext.save();
    var s = Math.max(this.zoomX * SCALE * costume.scale, this.zoomY * SCALE * costume.scale);
    this.backdropContext.scale(s, s);    
    this.backdropContext.drawImage(costume.image, 0, 0, costume.image.width/costume.resScale, costume.image.height/costume.resScale);    
    this.backdropContext.restore();
    */
    
    var imgInfo = costume.image.imgInfo;
    
    glDrawImage(
      this.backdropContext,
      this.backdropContext.imgShaderInfo,
      this.backdropContext.imgBuffers,
      imgInfo,
      //(imgInfo.width / costume.resScale / 2 - costume.rotationCenterX / 480 * imgInfo.width / costume.resScale) * costume.scale,
      (imgInfo.width / costume.resScale * costume.scale - 480) / 2,
      -(imgInfo.height / costume.resScale * costume.scale - 360) / 2,
      imgInfo.width / costume.resScale * costume.scale,
      imgInfo.height / costume.resScale * costume.scale,
      0,
      0,
      0);
  };
	
  Stage.prototype.updateFilters = function() {
	  
    this.backdropCanvas.style.opacity = Math.max(0, Math.min(1, 1 - this.filters.ghost / 100));
  };

  Stage.prototype.setZoom = function(zoomX, zoomY) {
    if ((this.zoomX === zoomX) && (this.zoomY === zoomY)) return;
	  var ps = Math.max(zoomX, zoomY);
    if ((this.maxZoomX < zoomX * SCALE) || (this.maxZoomY < zoomY * SCALE)) {
      this.maxZoomX = zoomX * SCALE;
	    this.maxZoomY = zoomY * SCALE;
      //var canvas = document.createElement('canvas');
      //canvas.width = this.penCanvas.width;
      //canvas.height = this.penCanvas.height;
      //canvas.getContext('2d').drawImage(this.penCanvas, 0, 0);
      
      var imgInfo = glMakeTexture(this.penContext, this.penCanvas);

      this.penCanvas.width = 480 * ps * SCALE;
      this.penCanvas.height = 360 * ps * SCALE;
      
      glDrawImage(
        this.penContext,
        this.penContext.imgShaderInfo,
        this.penContext.imgBuffers,
        imgInfo,
        0,
        0,
        480,
        360,
        0,
        0,
        0);
      
      this.penContext.deleteTexture(imgInfo.texture);
      imgInfo = null;
      
      //this.penContext.drawImage(canvas, 0, 0, 480 * ps * SCALE, 360 * ps * SCALE);
      //this.penContext.scale(this.maxZoomX, this.maxZoomY);
      //this.penContext.lineCap = 'butt';	
    }
    
    
    this.canvas.width = 
    this.backdropCanvas.width =
    this.glCollisionContext.width = (480 * zoomX | 0);
    this.canvas.height = 
    this.backdropCanvas.height =
    this.glCollisionContext.height = (360 * zoomY | 0);
    
    
    this.root.style.width =
    this.canvas.style.width =
    this.backdropCanvas.style.width =
    this.glCollisionCanvas.style.width = (480 * zoomX | 0) + 'px';
    this.penCanvas.style.width = (480 * ps | 0) + 'px';
    this.root.style.height =
    this.canvas.style.height =
    this.backdropCanvas.style.height =
    this.glCollisionCanvas.style.height = (360 * zoomY | 0) + 'px';
    this.penCanvas.style.height = (360 * ps | 0) + 'px';
    this.root.style.fontSize = ps * 10 + 'px';
    this.zoomX = zoomX;
	  this.zoomY = zoomY;
    this.updateBackdrop();
  };

  Stage.prototype.clickMouse = function() {
    this.mouseSprite = undefined;
    for (var i = this.children.length; i--;) {
      var c = this.children[i];
      if (c.isSprite && c.visible && c.filters.ghost < 100 && c.touching('_mouse_')) {
        if (c.isDraggable) {
          this.mouseSprite = c;
          c.mouseDown();
        } else {
          this.triggerFor(c, 'whenClicked');
        }
        return;
      }
    }
    this.triggerFor(this, 'whenClicked');
  };

  Stage.prototype.releaseMouse = function() {
    this.mousePressed = false;
    if (this.mouseSprite) {
      this.mouseSprite.mouseUp();
      this.mouseSprite = undefined;
    }
  };

  Stage.prototype.stopAllSounds = function() {
    for (var children = this.children, i = children.length; i--;) {
      if (children[i].isSprite) {
        children[i].stopSounds();
      }
    }
    this.stopSounds();
  };

  Stage.prototype.removeAllClones = function() {
    var i = this.children.length;
    while (i--) {
      if (this.children[i].isClone) {
        this.children[i].remove();
        this.children.splice(i, 1);
      }
    }
  };

  Stage.prototype.getObject = function(name) {
    for (var i = 0; i < this.children.length; i++) {
      var c = this.children[i];
      if (c.objName === name && !c.isClone) {
        return c;
      }
    }
    if (name === '_stage_' || name === this.objName) {
      return this;
    }
  };

  Stage.prototype.getObjects = function(name) {
    var result = [];
    for (var i = 0; i < this.children.length; i++) {
      if (this.children[i].objName === name) {
        result.push(this.children[i]);
      }
    }
    return result;
  };

  Stage.prototype.draw = function() {
    var context = this.context;
	
    //this.canvas.width = 480 * this.zoomX * SCALE; // clear
    //this.canvas.height = 360 * this.zoomY * SCALE;
    
    context.clear(context.COLOR_BUFFER_BIT);
    
	  var s = Math.max(this.zoomX * SCALE, this.zoomY * SCALE);
    //context.scale(s, s);
    this.drawOn(context);
    
    if (this.hidePrompt) {
      this.hidePrompt = false;
      this.prompter.style.display = 'none';
      this.canvas.focus();
    }
    
		if(this.penCoordIndex){
      this.renderPen(this.penContext, this.penContext.penShaderInfo, this.penContext.penBuffers);
      this.penCoordIndex = 0;
      this.penLineIndex  = 0;
      this.penColorIndex = 0;
		}
  };

  Stage.prototype.drawOn = function(context, except) {
    for (var i = 0; i < this.children.length; i++) {
      if (this.children[i].visible && this.children[i] !== except ){
        this.children[i].draw(context);
      }
    }
  };

  Stage.prototype.drawAllOn = function(context, except) {
    var costume = this.costumes[this.currentCostumeIndex];
    
    /*
    context.save();
    context.scale(costume.scale, costume.scale);
    context.globalAlpha = Math.max(0, Math.min(1, 1 - this.filters.ghost / 100));    
    context.drawImage(costume.image, 0, 0, costume.image.width/costume.resScale, costume.image.height/costume.resScale);
    context.restore();
    */
 
    var tempTest = performance.now();
 
    glDrawImage(
      context,
      context.imgShaderInfo,
      context.imgBuffers,
      costume.image.collisionImgInfo,
      0,
      0,
      480,
      360,
      0,
      0,
      0);


      
    /*
    context.save();
	  var s = Math.max(this.maxZoomX, this.maxZoomY);
    context.scale(1 / s, 1 / s);
    context.drawImage(this.penCanvas, 0, 0);
    context.restore();
    */
    

    
    var imgInfo = glMakeTexture(context, this.penCanvas);
    
    glDrawImage(
      context,
      context.imgShaderInfo,
      context.imgBuffers,
      imgInfo,
      0,
      0,
      480,
      360,
      0,
      0,
      0);
      
    context.deleteTexture(imgInfo.texture);
    imgInfo = null;

    
    //console.log(performance.now() - tempTest);
    
    //this.drawOn(context, except);
    

    
    for (var i = 0; i < this.children.length; i++) {
      if (this.children[i].visible && this.children[i] !== except && !(this.children[i] instanceof Watcher)) {
        this.children[i].draw(context);
      }
    }    
  };

  Stage.prototype.moveTo = function() {};

  Stage.prototype.submitPrompt = function() {
    if (this.promptId < this.nextPromptId) {
      this.answer = this.prompt.value;
      this.promptId += 1;
      if (this.promptId >= this.nextPromptId) {
        this.hidePrompt = true;
      }
    }
  };
  
  Stage.prototype.renderPen = function(gl, programInfo, buffers){       
    gl.viewport(0, 0, this.penCanvas.width, this.penCanvas.height);
    
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    gl.enable(gl.BLEND);
    gl.disable(gl.DEPTH_TEST);
		
    //set up position buffer for coordinates
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
    gl.bufferData(gl.ARRAY_BUFFER,
                  this.penCoords,
                  gl.STREAM_DRAW);
    gl.vertexAttribPointer(
      programInfo.attribLocations.vertexData,
      4,
      gl.FLOAT,
      false,
      0,
      0);
    gl.enableVertexAttribArray(programInfo.attribLocations.vertexData);

    //set up line description buffer
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.line);
    gl.bufferData(gl.ARRAY_BUFFER,
                  this.penLines,
                  gl.STREAM_DRAW);
    gl.vertexAttribPointer(
      programInfo.attribLocations.lineData,
      2,
      gl.FLOAT,
      false,
      0,
      0);
    gl.enableVertexAttribArray(programInfo.attribLocations.lineData);
    
    //set up color buffer
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
    gl.bufferData(gl.ARRAY_BUFFER,
                  this.penColors,
                  gl.STREAM_DRAW);
    gl.vertexAttribPointer(
      programInfo.attribLocations.colorData,
      4,
      gl.FLOAT,
      false,
      0,
      0);
    gl.enableVertexAttribArray(programInfo.attribLocations.colorData);
		
    //draw pen lines as triangles.
    gl.useProgram(programInfo.program);
    gl.drawArrays(gl.TRIANGLES, 0, (this.penCoordIndex + 1) / 4);    
    
    /*
    var err
    if(err = gl.getError())
      console.log('WebGL Error: ' + err);
    */
  }